﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Threading;
using System.Windows.Threading;
using System.IO;
using Copy4Backup.Enums;
using System.Media;

namespace Copy4Backup
{
	/// <summary>
	/// Interaction logic for ExecuteWindow.xaml
	/// </summary>
	public partial class ExecuteWindow : Window
	{
		/// <summary>
		/// State values.
		/// </summary>
		enum State
		{
			Ready,
			DefineReparsePoints,
			Copying,
			Stop,
			Cancel,
			Error
		}

		/// <summary>
		/// Action values.
		/// </summary>
		enum Action
		{
			StartCopy,
			StopCopy,
			Cancel
		}

		#region Fields

		/// <summary>
		/// Holds the window title.
		/// </summary>
		private readonly string titleValue_;

		/// <summary>
		/// True, if the copying is in progress.
		/// </summary>
		private bool copyingIsRunning_;

		/// <summary>
		/// State change event.
		/// </summary>
		private event EventHandler<ExecuteEventArgs> executeEvent_;

		private readonly Project project_;

		private long maxSize_;

		private object enumeratorLockObject_;

		private FileWalker.FileWalkEnumerator enumerator_;

		private bool execute_;

		#endregion Fields

		#region Methods

		/// <summary>
		/// Prevents default ctor usage.
		/// </summary>
		private ExecuteWindow()
		{
		}

		public ExecuteWindow(Project project)
		{
			InitializeComponent();

			titleValue_ = Title;

			project_ = project;

			executeEvent_ = new EventHandler<ExecuteEventArgs>(ExecuteEventHandler);

			enumeratorLockObject_ = new object();
		}

		/// <summary>
		/// Defines the reparser points.
		/// </summary>
		private void DefineReparsePoints()
		{
			Logger.Log("==>");

			List<String> targetDirs = new List<string>();

			foreach (String sourceFolder in project_.SourceFolders)
			{
				try
				{
					string[] subfolders = Directory.GetDirectories(sourceFolder);

					foreach (string subfolder in subfolders)
					{
						FileAttributes attrs = File.GetAttributes(subfolder);

						if (((attrs & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint))
						{
							string targetDir = FileWalker.FileWalkEnumerator.GetTargetDir(new DirectoryInfo(subfolder));

							if (null != targetDir)
							{
								// Check, if the target directory doesn't exists in the source folder list.
								if (!project_.SourceFolders.Contains(targetDir))
								{
									targetDirs.Add(targetDir);

								}
							}
						}
					}
				}
				catch (UnauthorizedAccessException excp)
				{
					MessageBox.Show(String.Format("Unauthorized access to folder {0}: {1}", sourceFolder, excp.Message.ToString()), "Copying file", MessageBoxButton.OK, MessageBoxImage.Error);
				}
				catch (ArgumentException excp)
				{
					MessageBox.Show(String.Format("Invalid path {0}: {1}", sourceFolder, excp.Message.ToString()), "Copying file", MessageBoxButton.OK, MessageBoxImage.Error);
				}
				catch (PathTooLongException excp)
				{
					MessageBox.Show(String.Format("Too long path {0}: {1}", sourceFolder, excp.Message.ToString()), "Copying file", MessageBoxButton.OK, MessageBoxImage.Error);
				}
				catch (DirectoryNotFoundException excp)
				{
					MessageBox.Show(String.Format("Not existing path {0}: {1}", sourceFolder, excp.Message.ToString()), "Copying file", MessageBoxButton.OK, MessageBoxImage.Error);
				}
				catch (IOException excp)
				{
					MessageBox.Show(String.Format("The path {0} is a file: {1}", sourceFolder, excp.Message.ToString()), "Copying file", MessageBoxButton.OK, MessageBoxImage.Error);
				}
			}

			foreach (string targetDir in targetDirs)
			{
				project_.AddSourceFolder(targetDir);
				Logger.Log(String.Format("New source folder / target dir was added: \"{0}\"", targetDir));
			}
			Logger.Log("<==");
		}

		private void buttonStart_Click(object sender, RoutedEventArgs e)
		{
			bool isOK;

			// Check, a destination exists.
			if (!(isOK = Directory.Exists(project_.DestinationFolder)))
			{
				if (MessageBox.Show("The destination folder " + project_.DestinationFolder + " doesn't exist."
					+ System.Environment.NewLine + "Do you want to create it?",
					titleValue_, MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK)
				{
					try
					{
						Directory.CreateDirectory(project_.DestinationFolder);
						isOK = true;
					}

					catch (UnauthorizedAccessException excp)
					{
						MessageBox.Show(String.Format("The caller does not have the required permission.{0}Error: {1}",
							System.Environment.NewLine, excp.Message),
							titleValue_, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (ArgumentException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" is not valid.{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							titleValue_, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (PathTooLongException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" is too long valid.{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							titleValue_, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (DirectoryNotFoundException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" is not valid.{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							titleValue_, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (NotSupportedException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" contains a colon character (:) that is not part of a drive label (\"C:\\\")..{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							titleValue_, MessageBoxButton.OK, MessageBoxImage.Error);
					}

					catch (IOException excp)
					{
						MessageBox.Show(String.Format("The directory specified by \"{0}\" is read-only.{1}Error: {2}",
							project_.DestinationFolder, System.Environment.NewLine, excp.Message),
							titleValue_, MessageBoxButton.OK, MessageBoxImage.Error);
					}
				}
			}

			if (isOK)
			{
				// Check, if the destination is empty.

				if (!(isOK = (0 == Directory.GetDirectories(project_.DestinationFolder).Length)))
				{
					if (MessageBox.Show("The destination folder " + project_.DestinationFolder + " is not empty."
						+ System.Environment.NewLine + "Do you want to clear the destination folder?",
						titleValue_, MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK)
					{
						try
						{
							Directory.Delete(project_.DestinationFolder, true);
							Directory.CreateDirectory(project_.DestinationFolder);
							isOK = true;
						}

						catch (UnauthorizedAccessException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The caller does not have the required permission to delete the folder (\"{0}\").{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								titleValue_, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}

						catch (ArgumentException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The folder name (\"{0}\") is not valid.{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								titleValue_, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}

						catch (PathTooLongException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The folder name (\"{0}\") is not valid.{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								titleValue_, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}

						catch (DirectoryNotFoundException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The folder (\"{0}\") doesn't exist or is not valid.{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								titleValue_, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}

						catch (IOException excp)
						{
							System.Windows.MessageBox.Show(String.Format("Can't delete the folder (\"{0}\").{1}Error: {2}",
								project_.DestinationFolder.ToString(),
								System.Environment.NewLine, excp.Message),
								titleValue_, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
						}
					}
				}
			}

			if (isOK)
			{
				ComboBoxItem item = null;

				try
				{
					isOK = false;
					item = comboBoxMaxSize.SelectedItem as ComboBoxItem;
					if (null != item)
					{
						maxSize_ = long.Parse(item.Tag.ToString()) * 1024 * 1024; // MB to bytes.
						progressBarDiskUsage.Minimum = 0.0;
						progressBarDiskUsage.Maximum = maxSize_;
						progressBarDiskUsage.Value = 0.0;
						isOK = true;
					}
				}

				catch (FormatException excp)
				{
					System.Windows.MessageBox.Show(String.Format("The max (\"{0}\") has not valid format.{1}Error: {2}",
								item.Tag.ToString(),
								System.Environment.NewLine, excp.Message),
								titleValue_, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
				}

				catch (OverflowException excp)
				{
					System.Windows.MessageBox.Show(String.Format("The max (\"{0}\") has not valid value.{1}Error: {2}",
								item.Tag.ToString(),
								System.Environment.NewLine, excp.Message),
								titleValue_, System.Windows.MessageBoxButton.OK,
								System.Windows.MessageBoxImage.Error);
				}
			}

			if (isOK)
			{
				ThreadPool.QueueUserWorkItem(new WaitCallback(Processor), Action.StartCopy);
			}
		}

		private void buttonStop_Click(object sender, RoutedEventArgs e)
		{
			ThreadPool.QueueUserWorkItem(new WaitCallback(Processor), Action.StopCopy);
		}

		private void Window_Loaded(object sender, RoutedEventArgs e)
		{
			textBlockProjectName.Text = project_.Name;
			textBlockDiskNumber.Text = String.Empty;
			textBlockFileName.Text = String.Empty;
			textBlockTotalDiskSize.Text = String.Empty;

			SetState(State.Ready);
		}

		private void ExecuteEventHandler(object sender, ExecuteEventArgs args)
		{
			if (this.Dispatcher.Thread != System.Threading.Thread.CurrentThread)
			{
				this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
				(ThreadStart)delegate()
				{
					ProcessEvent(args);
				}
				);
			}
			else
			{
				ProcessEvent(args);
			}
		}

		private void ProcessEvent(ExecuteEventArgs args)
		{
			switch (args.Action)
			{
				case ExecuteEvent.CopyFile:
					textBlockFileName.Text = args.FileName;
					textBlockTotalDiskSize.Text = args.Disk.Used.ToString("N0");
					textBlockDiskNumber.Text = args.Disk.Number.ToString();
					progressBarDiskUsage.Value = args.Disk.Used;
					break;
				case ExecuteEvent.Completed:
					SetState(State.Ready);
					{
						SoundPlayer player = new SoundPlayer(Copy4Backup.Properties.Resources.Signal);
						player.Play();
					}
					MessageBox.Show("Copying was completed", Title, MessageBoxButton.OK, MessageBoxImage.Information);
					break;
				default:
					break;
			}
		}

		private void MakeFileCopy()
		{
			copyingIsRunning_ = true;

			FileChangedHandler fch = new FileChangedHandler(project_.DestinationFolder, maxSize_);
			fch.FileChangedEvent += new EventHandler<FileChangedEventArgs>(FileChangedEventHandler);

			foreach (String folderFilter in project_.FolderFilters)
			{
				fch.AddFolderFilter(new FolderFilter(folderFilter));
			}

			foreach (String fileFilter in project_.FileFilters)
			{
				fch.AddFileFilter(new FileFilter(fileFilter));
			}

			bool isOK = false;

			foreach(String sourceFolder in project_.SourceFolders)
			{
				if (execute_)
				{
					lock (enumeratorLockObject_)
					{
						enumerator_ = new FileWalker.FileWalkEnumerator(sourceFolder);
					}

					enumerator_.AddHandler(fch);
					isOK |= enumerator_.Enumerate();

					lock (enumeratorLockObject_)
					{
						enumerator_ = null;
					}	
				}
			}

			if (execute_ && isOK)
			{
				ExecuteEventArgs args = new ExecuteEventArgs(ExecuteEvent.Completed);
				executeEvent_(this, args);	
			}

			copyingIsRunning_ = false;

		}

		void FileChangedEventHandler(object sender, FileChangedEventArgs e)
		{
			ExecuteEventArgs args = new ExecuteEventArgs(ExecuteEvent.CopyFile);
			args.FileName = e.FileInfoData.FullName;
			args.Disk = e.Disk;
			executeEvent_(this, args);
		}

		private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			if (copyingIsRunning_)
			{
				execute_ = false;

				lock (enumeratorLockObject_)
				{
					if (null != enumerator_)
					{
						enumerator_.Stop();
					}
				}

				while (copyingIsRunning_)
				{
					Thread.Sleep(100);
				}
			}
		}

		private void Processor(object action)
		{
			switch ((Action)action)
			{
				case Action.Cancel:
					MakeCancel();
					break;
				case Action.StartCopy:
					MakeDefineReparsePoints();
					MakeStartCopy();
					break;
				case Action.StopCopy:
					MakeStopCopy();
					break;
			}
		}

		private void MakeCancel()
		{
			
			Dispatcher.Invoke(DispatcherPriority.Normal, new WaitCallback(SetState), State.Cancel);
			//todo
			Dispatcher.Invoke(DispatcherPriority.Normal, new WaitCallback(SetState), State.Ready);
		}

		private void MakeDefineReparsePoints()
		{
			Dispatcher.Invoke(DispatcherPriority.Normal, new WaitCallback(SetState), State.DefineReparsePoints);
			DefineReparsePoints();
			Dispatcher.Invoke(DispatcherPriority.Normal, new WaitCallback(SetState), State.Ready);
		}

		private void MakeStartCopy()
		{
			Dispatcher.Invoke(DispatcherPriority.Normal, new WaitCallback(SetState), State.Copying);
			execute_ = true;
			//workThread_ = new Thread(MakeFileCopy);
			//workThread_.Start();
			MakeFileCopy();

			Dispatcher.Invoke(DispatcherPriority.Normal, new WaitCallback(SetState), State.Ready);
		}

		private void MakeStopCopy()
		{
			Dispatcher.Invoke(DispatcherPriority.Normal, new WaitCallback(SetState), State.Stop);
			execute_ = false;

			lock (enumeratorLockObject_)
			{
				if (null != enumerator_)
				{
					enumerator_.Stop();
				}
			}

			while (copyingIsRunning_)
			{
				Thread.Sleep(100);
			}
			Dispatcher.Invoke(DispatcherPriority.Normal, new WaitCallback(SetState), State.Ready);
		}

		private void SetState(object state)
		{
			switch ((State)state)
			{
				case State.Copying:
					labelStatus.Content = "Copying...";
					buttonStart.IsEnabled = false;
					buttonStop.IsEnabled = true;
					buttonCancel.IsEnabled = false;
					break;
				case State.DefineReparsePoints:
					labelStatus.Content = "Define reparse points...";
					buttonStart.IsEnabled = false;
					buttonStop.IsEnabled = true;
					buttonCancel.IsEnabled = false;
					break;
				case State.Error:
					labelStatus.Content = "***ERROR";
					break;
				case State.Ready:
					labelStatus.Content = "Ready";
					buttonStart.IsEnabled = true;
					buttonStop.IsEnabled = false;
					buttonCancel.IsEnabled = true;
					break;
				case State.Stop:
					labelStatus.Content = "Stopping...";
					break;
			}
		}
		#endregion Methods
	}
}
